package station;

import java.io.IOException;
import java.net.*;
import java.util.ArrayList;
import java.util.LinkedList;

public class SenderThread implements Runnable {

    InetAddress group;
    MulticastSocket socket;
    byte[] userBuf;
    private boolean[] slots;
    private final int PORT;
    private long OFFSET = 0;
    private final int SLOTCOUNT = 20;
    private final String netwInterface;
    private boolean socketClosed = false;

    
    
    LinkedList<Long> offsets;
    LinkedList<DatagramPacket> udpData;

    boolean running;

    byte lastSlot = 0;
    byte nextSlot = 0;

    public SenderThread(String mcastaddress, int port, String netwInterface) {
    	this.netwInterface = netwInterface;
        slots = new boolean[SLOTCOUNT];
        resetSlots();
        this.PORT = port;
        offsets = new LinkedList<Long>();
        udpData = new LinkedList<DatagramPacket>();
        try {
            socket = new MulticastSocket(PORT);
            if(!(this.netwInterface.equals(""))){		//Interface setzen
            	socket.setInterface(InetAddress.getByName(netwInterface));
            }
            group = InetAddress.getByName(mcastaddress);
            socket.joinGroup(group);
        } catch (IOException e) {
            e.printStackTrace();
        }
        userBuf = new byte[33];
        setBufNull(userBuf);
        running = true;
    }

    public void run() {

        //beim Starten:
        try {
            Thread.sleep(1985 - (System.currentTimeMillis() % 1000));	//warten, bis angebrochener und folgender (kompletter) Frame beendet sind
        } catch (InterruptedException e1) {
            e1.printStackTrace();
        }
        nextSlot = getSlotNextF();		//Ersten Slot holen
        System.out.println("ST: Erster Slot: " + nextSlot);
        lastSlot = nextSlot;

        long firstSleep = (50*nextSlot)+30;	//Warten, bis erster Slot erreicht wird
        try {
            Thread.sleep(firstSleep);
        } catch (InterruptedException e1) {
            e1.printStackTrace();
        }
        while (running) {

        	//System.out.println("OFFSET: " + OFFSET);
//            double tmp = nextSlot + 0.45;
//            while(((System.currentTimeMillis() + OFFSET) % 1000 / 50.0) < tmp){
//                //warten
//            }
            //			System.out.println("ST: zeitlicher Slot: " + (System.currentTimeMillis() % 1000 / 50.0) + "; Berechneter Slot: " + nextSlot);
            nextSlot = getSlotNextF();
           //   System.out.println("ST: NextSlot: [ " + nextSlot  + " ]; currentSlot: [ " + lastSlot + " ]");
            if(nextSlot != -1){

                //Daten f�r Sendung an Multicast zusammenfassen
                setNutzData(userBuf);
                setNextSlot(userBuf, nextSlot);
                setTimeStamp(userBuf);

                DatagramPacket sendData = new DatagramPacket(userBuf, userBuf.length, group, PORT);
                try {
                	if(!socketClosed){
    					socket.send(sendData);
    				}else{
    					socket.close();
    				}
                } catch (IOException e) {
                    e.printStackTrace();
                }

                setBufNull(userBuf);	//Buffer pr�ventiv bereinigen


                long timeToSleep = (50 * nextSlot) + 25 - OFFSET;
                


                lastSlot = nextSlot;

                try {
                	Thread.sleep((1000 - (System.currentTimeMillis() % 1000)) + timeToSleep);
                } catch (InterruptedException e1) {
                    e1.printStackTrace();
                }
            }else{
                System.out.println("Kein Slot gefunden ... Shutdown");
                terminate();
            }
        }
    }

    public synchronized void setContent(byte[] udpContent){			//aufgerufen vom UDPWorker
        byte content[] = new byte[24];
        for(int i = 0; i<24; i++){
            content[i] = udpContent[i];
        }
        this.udpData.offer((new DatagramPacket(content, content.length, group, PORT)));
    }

    public void setNutzData(byte[] buf){
        if(udpData.isEmpty()){
            buf[0] = 'E';
        }else{
            for(int i = 0; i < 24; i++){
                buf[i] = udpData.getFirst().getData()[i];
            }
            udpData.remove();		//gelesenen Eintrag entfernen
        }
    }

    public void setNextSlot(byte[] buf, byte slot){
        buf[24] = slot;
    }

    public void setTimeStamp(byte[] buf){
        long time = System.currentTimeMillis();
        for(int i = 0; i < 8; i++){
            buf[25+i] = (byte)((time>>((7-i)*8))&0xFF);
        }
    }

    public void setRunning(boolean running) {
        this.running = running;
    }

    public void terminate(){
    	this.socketClosed = true;
        running = false;
        System.out.println("SenderThread...");
    }

    private void setBufNull(byte[] buf) {
        for (int i = 0; i < buf.length; i++) {
            buf[i] = 0;
        }
    }

    public void setUserBuf(byte[] userBuf) {
        this.userBuf = userBuf;
    }

    public synchronized void setSlotFalse(int slot){
        slots[slot] = false;
    }

    public synchronized void resetSlots(){
        for(int i = 0; i < slots.length ; i++){
            slots[i] = true;
        }
    }

    public synchronized byte getSlotNextF(){
        ArrayList<Byte> list = new ArrayList<Byte>();

        //freie Slots in list schreiben
        for(byte i = 0; i < slots.length; i++){
            if(slots[i]){
                list.add(i);
            }
        }

        if(!list.isEmpty()){
            //in List stehen freie Slots, nun wird daraus zuf�llig einer ausgew�hlt
            byte slot = lastSlot;
        	while(lastSlot == slot){
        		slot = (byte)(Math.random() * list.size());
        	}
            return list.get(slot);
        }else{
            return -1;
        }
    }
    
    
    public void setOFFSET(long offset){
        offsets.add(offset);
        if(offsets.size() > 10){
            offsets.remove();
        }
        
        long offsetSum = 0;
        for(int i = 0; i < offsets.size(); i++){
            offsetSum += offsets.get(i);
        }
        
        OFFSET = (long)(offsetSum / offsets.size());
        //System.out.println("OFFSET: " + OFFSET);
    }
    



}
